library ieee;
use ieee.std_logic_1164.all;
use work.processor_types.all;

entity control is
	port (
		clock: in std_ulogic;
        reset: in std_ulogic;
		opcode: in bit6;
        func: in bit6;
		reg_s : in bit32;
		alu_control: out alu_bus;
		mux_control: out std_ulogic; -- 1=load value from mem at program counter 0=load value from mem at instruction (output alu)
        reg_rw_sel : out std_ulogic; -- 1=write 0=read
        pc_jump_sel : out std_ulogic; -- 1=jump 0=no jump
        pc_branch_sel : out std_ulogic;
        incr_pc : out std_ulogic; -- 1=increment pc 0=do not increment pc
        ir_sel : out std_ulogic; -- 1=decode instruction
        alu_in_sel : out alu_input_select;
        reg_write_source : out std_ulogic; -- 1=alu_result,rd (normal writeback) 0=d_busin, rt (memory load)
        write : out std_ulogic;
        read : out std_ulogic;
        ready : in std_ulogic;
		done : out std_ulogic
	);
end control;
	
architecture arch_control of control is
    type control_states is
        (init, instr_fetch, decode, execute, exec_load, exec_store, writeback, mem_load, mem_store, multiply_state_1, multiply_state_2);
	signal state: control_states;
	signal reg_s_ext : bit33;
begin
    -- psl sequence normal_exec is {state=execute; state=writeback};
	-- psl sequence mem_exec is {state=execute; (state=exec_load) or (state=exec_store); ((state=mem_load) or (state=mem_store))[+]; state=writeback}; 
	-- psl sequence mult_exec is {state=multiply_state_1; state=multiply_state_2[*32]; state=writeback};
	
	-- psl property states is 
	--   always ({reset='0' and (state=init)} |=> {state=instr_fetch[+]; state=decode; {normal_exec} | {mem_exec} | {mult_exec}}) @rising_edge(clock);
	-- psl assert states;
	
	-- psl property psl_memory is
	--   always ((read or write) -> eventually! ready) @rising_edge(clock);
	-- psl assert psl_memory;
	
	-- psl property psl_memory_rw is
	--   never ({read and write}) @rising_edge(clock);
	-- psl assert psl_memory_rw;
	
	-- psl property inc_pc is
	--   always ({(incr_pc='1') = (state=decode)}) @rising_edge(clock);
	-- psl assert inc_pc;
	
	-- psl property psl_mem_write is
	--   always ((write='1') = (state=mem_store)) @rising_edge(clock);
	-- psl assert psl_mem_write;
	
	-- psl property psl_mem_abus is
	--   always ((mux_control='0') = (state=mem_store or state=mem_load or state=exec_store or state=exec_load)) @rising_edge(clock);
	-- psl assert psl_mem_abus;
	
	
	seq: process(clock, reset)
		variable count : natural range 1 to 32;
	begin
        if (reset='1') then
            state <= init;
            read<='0'; write<='0'; mux_control<='1'; pc_jump_sel<='0'; pc_branch_sel<='0'; incr_pc<='0'; ir_sel<='0';
            alu_control<=(others=>'0');
            done <= '0';
			reg_s_ext <= (others => '0');
            reg_rw_sel<='0'; alu_in_sel<=rs_rt; reg_write_source<='0';
		elsif (rising_edge(clock)) then
			case state is
                when init =>
                                read<='1'; ir_sel <= '1'; state<=instr_fetch; reg_rw_sel <= '0'; done <= '0'; alu_control<=(others=>'0');
                when instr_fetch =>
                                if (ready = '1') then
                                    state <= decode;
									incr_pc<='1'; 
                                end if;
                when decode =>
								incr_pc<='0';
                                read <= '0';
                                ir_sel <= '0';
                                case opcode is
                                    when rtype =>
                                            reg_write_source <='1';
                                            
                                            case func is
                                                when add    => alu_in_sel <= rs_rt; alu_control(addition)<='1'; state <= execute;
                                                when mfhi   => alu_in_sel <= rs_rt; alu_control(gethigh)<='1'; state <= execute;
                                                when mflo   => alu_in_sel <= rs_rt; alu_control(getlow)<='1'; state <= execute;
                                                when mult   => alu_in_sel <= res_rt; alu_control(init_mult)<='1'; state <= multiply_state_1;
                                                when f_sll  => alu_in_sel <= rt_rh; alu_control(shiftleft)<='1'; state <= execute;
                                                when sltu   => alu_in_sel <= rs_rt; alu_control(setltu)<='1'; alu_control(subtract)<='1'; state <= execute;
                                                when f_nor  => alu_in_sel <= rs_rt; alu_control(not_or)<='1'; state <= execute;
                                                when others => state <= execute;
                                            end case;
                                    when blez => alu_control(addition)<='1'; alu_in_sel<=pc_imm; state <= execute;
                                    when j => alu_control(addition)<='1'; alu_in_sel<=pc_address; state <= execute;
                                    when lw => alu_in_sel<=rs_imm; reg_write_source<='0'; alu_control(addition)<='1'; state <= execute;
                                    when sw => alu_in_sel<=rs_imm; alu_control(addition)<='1'; state <= execute;
                                    when others => state <= execute;
                                end case;
                when execute => 
                                case opcode is
                                    when rtype => reg_rw_sel<='1'; state <= writeback; --state <= writeback_finished;
                                    when blez => pc_branch_sel<='1'; state <= writeback;
                                    when j => pc_jump_sel<='1'; state <= writeback;
                                    when lw => state <= exec_load; mux_control<='0'; 
                                    when sw => state <= exec_store; mux_control<='0';
                                    when others => state <= writeback;
                                end case;
                when multiply_state_1 =>
								alu_control(init_mult) <= '0';
								reg_s_ext <= reg_s & '0';
								count := 32;
								state <= multiply_state_2;
				when multiply_state_2 =>
								case reg_s_ext(1 downto 0) is
									when "01" => alu_control <= (addition=>'1', shiftright=>'1', others=>'0'); alu_in_sel <= res_rt;
									when "10" => alu_control <= (subtract=>'1', shiftright=>'1', others=>'0'); alu_in_sel <= res_rt;
									when others => alu_control <= (shiftright=>'1', others=>'0'); alu_in_sel <= res_null;
								end case;
								reg_s_ext <= '0' & reg_s_ext(32 downto 1);
								if (count = 1) then
									state <= writeback;
								else
									count := count - 1;
								end if;
                when exec_load => read<='1'; state <= mem_load;
                when exec_store => write<='1'; state <= mem_store;
                when mem_load =>
                                if (ready = '1') then
                                    reg_rw_sel <= '1';
									mux_control <= '1';
                                    state <= writeback;
                                end if;
                when mem_store => 
                                if (ready = '1') then
                                    write <= '0';
									mux_control <= '1';
                                    state <= writeback;
                                end if;
                when writeback => alu_control <= (others => '0'); pc_jump_sel<='0'; pc_branch_sel<='0'; read <= '0'; reg_rw_sel <= '0'; state <= init; done<='1';
                when others =>
            end case;
		end if;
	end process;
end arch_control;
